<!DOCTYPE html>
<meta charset="utf-8">
<style>

.svg-container {
  display: inline-block;
  position: relative;
  width: 100%;
  padding-bottom: 100%; /* aspect ratio */
  vertical-align: top;
  overflow: hidden;
}

.svg-content-responsive {
  display: inline-block;
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
}

</style>
<svg></svg>
<script src="/static/d3.v4.min.js"></script>
<script type="text/javascript">
function export_svg(){
    var e = document.createElement('script')
    e.setAttribute('src', '/static/svg-crowbar.js')
    e.setAttribute('class', 'svg-crowbar')
    document.body.appendChild(e)
}
</script>
<button onclick="export_svg()">Export SVG</button>
<button onclick="halt_sim()">Pause</button>
<button onclick="restart_sim()">Continue</button>
<script>

var w = window,
    d = document,
    e = d.documentElement,
    g = d.getElementsByTagName('body')[0],
    width  = w.innerWidth  || e.clientWidth  || g.clientWidth,
    height = w.innerHeight || e.clientHeight || g.clientHeight,
    vbscale = 0.5

var svg = d3.select("svg")
  .classed("svg-content-responsive", true)
  .attr("preserveAspectRatio", "xMinYMin meet")
  .attr("viewBox", -vbscale * width + " " + -vbscale * height + " " + 2 * vbscale * width + " " + 2 * vbscale * height)
  .call(d3.zoom().scaleExtent([0.125, 8]).on("zoom", function(){ svg.attr("transform", d3.event.transform) }))
  .on("dblclick.zoom", null)
  .append("g")

function sortObject(obj) {
  if(obj.constructor !== Object)
    return obj
  var tmp = {}
  var keys = []
  for(var key in obj)
    keys.push(key)
  keys.sort()
  for(var index in keys)
    tmp[keys[index]] = sortObject(obj[keys[index]])
  return tmp
}

function sortTopObject(obj, preload) {
  var tmp = {}
  var keys = []
  for(var index in preload)
    tmp[preload[index]] = sortObject(obj[preload[index]])
  for(var key in obj)
    if(!(key in tmp))
      keys.push(key)
  keys.sort()
  for(var index in keys)
    tmp[keys[index]] = sortObject(obj[keys[index]])
  return tmp
}

var simulation,
    color = d3.scaleOrdinal(d3.schemeCategory20),
    uuid = window.location.pathname.substr(window.location.pathname.length - 36)

function restart_sim(){
    simulation.alpha(1).restart()
}

function halt_sim(){
    simulation.stop()
}

d3.json("/d3/" + uuid + window.location.search, function(error, graph) {
  if (error) throw error

  for(var i = graph.nodes.length - 1; i >= 0; i--){
    graph.nodes[i].neighbors = {}
  }

  for(var i = graph.edges.length - 1; i >= 0; i--){
    var e = graph.edges[i]
    graph.nodes[e.source].neighbors[e.target] = true
    graph.nodes[e.target].neighbors[e.source] = true
  }

  graph.max_nc = 0
  for(var i = graph.nodes.length - 1; i >= 0; i--){
    var c = graph.nodes[i]
    c.nc = Object.keys(c.neighbors).length
    if(c.nc > graph.max_nc)
      graph.max_nc = c.nc
  }

  for(var i = graph.nodes.length - 1; i >= 0; i--){
    var c = graph.nodes[i]
    c.max_neighbor_nc = 0
    for(var neighbor in graph.nodes[i].neighbors){
      var n = graph.nodes[Number(neighbor)]
      if(n.nc > c.max_neighbor_nc)
        c.max_neighbor_nc = n.nc
    }
  }

  var link = svg.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(graph.edges)
    .enter().append("line")
      .attr("stroke-width", function(d) { return d.mark ? 3 : 2 })
      .attr("stroke-opacity", function(d) { return d.mark ? 1 : 0.2 })
      .attr("stroke", function(d){ return d.mark ? "#0e2" : "#222" })

  var node = svg.append("g")
    .attr("class", "nodes")
    .selectAll("circle")
    .data(graph.nodes)
    .enter().append("circle")
      .attr("r", function(d){ return 5 + (graph.max_nc ? 4*Math.sqrt(clamp_nc(d.nc)/graph.max_nc) : 0) })
      .attr("fill", function(d) { return d.toobig ? "#000" : color(d.data.type) })
      .attr("stroke", function(d) { return d.mark ? "#0e2" : "#fff" })
      .attr("stroke-width", function(d) { return d.mark ? 3 : 1 })
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended))

  link.append("title")
    .text(function(d) { return JSON.stringify(sortTopObject(d.data,['ID','type','value','srcID','tgtID']), null, 2) })

  node.append("title")
    .text(function(d) { return JSON.stringify(sortTopObject(d.data,['ID','type','value']), null, 2) })

  simulation = d3.forceSimulation()
    .velocityDecay(.12)
    .alphaDecay(0.02)
    .force("collide", d3.forceCollide(2))
    .force("center", d3.forceCenter())
    .force("fX", d3.forceX())
    .force("fY", d3.forceY())

  simulation
    .force("link", d3.forceLink().strength(function(link){
      var min = clamp_nc(Math.min(link.source.nc, link.target.nc))
      var f = 1
      if(min > 1)
        f = Math.pow((1 + graph.max_nc - clamp_nc(Math.min(link.source.max_neighbor_nc, link.target.max_neighbor_nc))) / graph.max_nc, .9)
      return f
    }))

    .force("charge", d3.forceManyBody().strength(function(n){
      var f = n.nc ? -30 : -10
      if(n.nc > 1)
        f = - 100 * (clamp_nc(n.nc) + clamp_nc(n.max_neighbor_nc)) / graph.max_nc
      return f
    }))

  function clamp_nc(nc){
    return Math.min(nc, graph.max_nc)
  }

  simulation
      .nodes(graph.nodes)
      .on("tick", ticked)

  simulation.force("link")
      .links(graph.edges)

  function ticked() {
    link
        .attr("x1", function(d) { return d.source.x })
        .attr("y1", function(d) { return d.source.y })
        .attr("x2", function(d) { return d.target.x })
        .attr("y2", function(d) { return d.target.y })

    node
        .attr("cx", function(d) { return d.x })
        .attr("cy", function(d) { return d.y })
  }
})

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.2).restart()
  d.fx = d.x
  d.fy = d.y
}

function dragged(d) {
  d.fx = d3.event.x
  d.fy = d3.event.y
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0)
  d.fx = null
  d.fy = null
}
</script>
